/*
 * Copyright (c) 2010 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * Name: KeyMatchReport.java
 *
 * Description: Command line application to display the defined KeyMatches
 * per front end.
 *
 * The class uses the Google Admin API for java. This requires the appliance to
 * be at least of software version 6.0. It will pull the KeyMatch information
 * from the specified front end (or all front ends if none are specified) and
 * display details about the defined KeyMatches.  It will optionally test if
 * the KeyMatches' associated URLs are able to be fetched.
 *
 * Please note that this class needs the following GData libraries:
 * - gdata-core-1.0.jar
 * - gdata-gsa-1.0.jar
 * - google-collect1.0-rc1.jar
 *
 * The libraries can be downloaded from:
 * http://code.google.com/p/google-enterprise-gdata-api/
 *
 * This class also needs the following Apache Commons library:
 * - commons-cli-1.2.jar
 *
 * This library can be downloaded from:
 * http://commons.apache.org/cli/download_cli.cgi
 *
 * To run the program use the following two commands:
 * 1) From the project's top level directory (the directory that contains the
 *   'src' folder) compile the project by running
 *   ant deploy
 *
 * 2) execute
 *   java -jar kmr.jar -h gsa1 -u admin -p secret
 *
 * Please note that the parameter settings are just examples. You will have to
 * replace them with your local appliance values.
 *
 * The usage message:
 * usage: java -jar kmr.jar [options]
 * -c,--csv               print results in CSV format instead of normal
 *                        report.
 * -f,--fe <feName>       the GSA front end name. If not provided, defaults
 *                        to all front ends
 * -h,--hostname <host>   the IP address or hostname of the GSA
 * -P,--port <portNum>    the port number of the GSA's admin interface.  If
 *                        not provided, defaults to 8000.
 * -p,--password <pw>     the user's password.  If not provided, the user
 *                        will be prompted for one.
 * -u,--username <user>   an administrator's username
 * -v,--verify            verify that the URLs are valid.
 */
package com.google.enterprise.gsa.keymatch;

import com.google.enterprise.apis.client.GsaEntry;
import com.google.enterprise.apis.client.GsaFeed;
import com.google.enterprise.apis.client.ImprovedGsaClient;
import com.google.enterprise.apis.client.ImprovedGsaClientFactory;
import com.google.enterprise.gsa.util.PasswordPrompt;
import com.google.gdata.util.AuthenticationException;
import com.google.gdata.util.ServiceException;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.cli.PosixParser;

import static java.lang.System.err;
import static java.lang.System.exit;
import static java.lang.System.out;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Map;

/**
 * Creates a report for KeyMatches on a GSA.<p />
 * For example:<pre>
 * KeyMatch Report for Front End 'default_frontend':
 * Query           URL                       KeyMatch Type        Title Link Status
 * --------------------------------------------------------------------------------
 * dinosaur        http://dinosaur.google.co KeywordMatch         Dinos Bad (-1)
 * maps            http://www.google.com/ent KeywordMatch    Google Geo OK (200)
 *
 * KeyMatch Report for Front End 'my_frontend':
 * Query           URL                       KeyMatch Type        Title Link Status
 * --------------------------------------------------------------------------------
 * Google          http://www.google.com/    KeywordMatch   Google Main OK (200)
 *
 * No KeyMatches found for Front End 'no_keymatches'.
 *</pre>
 */
public class KeyMatchReport {

  private static final String IO_ERROR_HAS_OCCURRED = "An IO error has occurred.";
  private static final String GSA_ADMIN_API_ERROR = "Error while accessing GSA Admin API service.";
  private static final char CSV_OPT = 'c';
  private static final char FE_OPT =  'f';
  private static final char PASS_OPT = 'p';
  private static final char PORT_OPT = 'P';
  private static final char USER_OPT = 'u';
  private static final char HOST_OPT = 'h';
  private static final char VERIFY_OPT = 'v';


  /*
   * Instance variable(s)
   */
  private ImprovedGsaClient gsaClient;

  public KeyMatchReport(ImprovedGsaClient client) {
    gsaClient = client;
  }

  /**
   * @return a list of all the front ends
   * @throws IOException
   * @throws ServiceException
   * @throws MalformedURLException
   */
  public ArrayList<String> getAllFrontEnds()
  throws MalformedURLException, ServiceException, IOException {

    //
    // grab all the front ends
    //
    GsaFeed feFeed = gsaClient.getFeed("frontend");

    //
    // add the front ends to the list
    //
    ArrayList<String> frontEnds = new ArrayList<String>();
    for (GsaEntry myEntry : feFeed.getEntries()) {
      frontEnds.add(myEntry.getGsaContent("entryID"));
    }

    return frontEnds;
  }

  /**
   * Gets a list of all the KeyMatches for the specified front end.
   *
   * @param frontEnd The front end from which to get KeyMatches
   * @return the list of KeyMatches that belong to that given frontend
   * @throws IOException
   * @throws ServiceException
   * @throws MalformedURLException
   */
  public ArrayList<KeyMatch> getKeyMatches(String frontEnd)
  throws MalformedURLException, ServiceException, IOException {
    //
    // Get all the keymatches for the frontEnd
    //
    GsaEntry myEntry = gsaClient.getEntry("keymatch", frontEnd);

    ArrayList<KeyMatch> keyMatches = new ArrayList<KeyMatch>();
    if (myEntry != null) {

      Map<String, String> contents = myEntry.getAllGsaContents();

      // TODO(todd.re...): Check for multiple pages?
      for (String key : contents.keySet()) {
        if (key.startsWith("numLines") || key.startsWith("entryID")) {
          continue;
        }

        String kmstr = contents.get(key);
        String[] components = kmstr.split(",");
        String term = components[0].trim();
        String type = components[1].trim();
        String url = components[2].trim();
        String title = components[3].trim();

        keyMatches.add(new KeyMatch(term, url, type, title));
      }
    }
    // An empty keyMatches is ok, too
    return keyMatches;
  }

  /**
   * @param args command-line arguments
   */
  public static void main(String[] args) {
    Options options = setUpOptions();
    CommandLineParser parser = new PosixParser();

    String username = null;
    String password = null;
    String hostname = null;
    int port = 8000;

    boolean printCSV = false;
    boolean verifyURLs = false;

    // The front end (optionally) specified on the CLI
    String cliFrontEnd = null;

    //
    // Set the variables from the CLI arguments
    //
    CommandLine line = null;
    try {
      line = parser.parse(options, args);
    } catch (ParseException e) {
      err.println(e.getMessage());
      HelpFormatter formatter = new HelpFormatter();
      formatter.printHelp("java -jar kmr.jar [options]", options);
      exit(1);
    }

    username = line.getOptionValue(USER_OPT);
    hostname = line.getOptionValue(HOST_OPT);
    //
    // Get the optional password from the CL
    //
    if (line.hasOption(PASS_OPT)) {
      password = line.getOptionValue(PASS_OPT);
    } else {
      password = PasswordPrompt.getPasswordViaConsole("Enter password for " + username + ": ");
    }

    //
    // Get the optional port from the CL
    //
    if (line.hasOption(PORT_OPT)) {
      port = Integer.parseInt(line.getOptionValue(PORT_OPT));
    }

    //
    // Get the frontend name from the CL
    //
    if (line.hasOption(FE_OPT)) {
      cliFrontEnd = line.getOptionValue(FE_OPT);
    }

    //
    // Get the optional CSV and URL verification flags from the CL
    //
    printCSV = line.hasOption(CSV_OPT);
    verifyURLs = line.hasOption(VERIFY_OPT);

    //
    // Connect to the GSA
    //
    ImprovedGsaClient client = null;
    try {
      client = ImprovedGsaClientFactory.create(hostname, port,
          username, password);
    } catch (AuthenticationException e) { // Bail if we can't authenticate
      err.println(e.getMessage());
      exit(1);
    }

    //
    // Get only the specified front ends, or all of them
    //
    KeyMatchReport keyMatchReport = new KeyMatchReport(client);
    ArrayList<String> frontEnds = new ArrayList<String>();
    if (cliFrontEnd != null && cliFrontEnd.length() > 0) {
      frontEnds.add(cliFrontEnd);
    } else {
      try {
        frontEnds = keyMatchReport.getAllFrontEnds();
      } catch (MalformedURLException e) {
        printErrorAndExit(e, 1);
      } catch (ServiceException e) {
        err.println(GSA_ADMIN_API_ERROR);
        printErrorAndExit(e, 1);
      } catch (IOException e) {
        err.println(IO_ERROR_HAS_OCCURRED);
        printErrorAndExit(e, 1);
      }
    }

    //
    // Get the KeyMatches for each front end in the list
    //
    ArrayList<KeyMatch> keyMatches = new ArrayList<KeyMatch>();
    for (String fe : frontEnds) {
      keyMatches.clear();
      try {
        keyMatches = keyMatchReport.getKeyMatches(fe);
      } catch (MalformedURLException e) {
        printErrorAndExit(e, 1);
      } catch (ServiceException e) {
        err.println(GSA_ADMIN_API_ERROR);
        printErrorAndExit(e, 1);
      } catch (IOException e) {
        err.println(IO_ERROR_HAS_OCCURRED);
        printErrorAndExit(e, 1);
      }
      
      assert keyMatches != null;
      if (keyMatches.isEmpty()) {
        out.println("No KeyMatches found for Front End '" + fe + "'.");
      } else {
        out.println("KeyMatch Report for Front End '" + fe + "':");
        printHeader(printCSV);
        for (KeyMatch keyMatch : keyMatches) {
          printKeyMatch(keyMatch, printCSV, verifyURLs);
        }
      }
      out.println(); // separate the results from each front end
    }
  }

  /*
   * Sets all the CLI flags and options (and their defaults if appropriate).
   */
  @SuppressWarnings("static-access")
  private static Options setUpOptions() {
    Options opts = new Options();
    opts.addOption(OptionBuilder.withArgName("pw").hasArg()
        .withLongOpt("password").withDescription("the user's password.  " +
            "If not provided, the user will be prompted for one.").create(PASS_OPT));
    opts.addOption(OptionBuilder.withArgName("host").hasArg().isRequired()
        .withLongOpt("hostname").withDescription("the IP address or hostname of the GSA")
        .create(HOST_OPT));
    opts.addOption(OptionBuilder.withArgName("user").hasArg().isRequired()
        .withLongOpt("username").withDescription("an administrator's username")
        .create(USER_OPT));
    opts.addOption(OptionBuilder.withArgName("portNum").hasArg()
        .withLongOpt("port").withDescription("the port number of the GSA's " +
            "admin interface.  If not provided, defaults to 8000.")
        .create(PORT_OPT));
    opts.addOption(OptionBuilder.withArgName("feName").hasArg()
        .withLongOpt("fe").withDescription("the GSA front end name. If not " +
            "provided, defaults to all front ends").create(FE_OPT));
    opts.addOption(OptionBuilder.withLongOpt("csv")
        .withDescription("print results in CSV format instead of normal report.")
        .create(CSV_OPT));
    opts.addOption(OptionBuilder.withLongOpt("verify")
        .withDescription("verify that the URLs are valid.")
        .create(VERIFY_OPT));
    return opts;
  }

  /*
   * Prints the output header like
   *
   * Query    URL   KeyMatch Type   Title   Link Status
   * --------------------------------------------------
   *
   * or for CSV format
   *
   * Query,URL,KeyMatch Type,Title,Link Status
   * -------------------------------------------
   */
  private static void printHeader(boolean printCSV) {
    String headerStr = String.format(getFormatString(printCSV), "Query", "URL",
        "KeyMatch Type", "Title", "Link Status");
    out.println(headerStr);
    if (!printCSV) {
      int width = 0;
      int headerLength = headerStr.length();
      do {
        out.print('-');
        width++;
      } while (width < headerLength); // Add one char for each space
      out.println();
    }
  }

  /*
   * Prints out the information about a given KeyMatch in the correct format
   */
  private static void printKeyMatch(KeyMatch keymatch, boolean printCSV, boolean verifyURL) {
    out.println(String.format(getFormatString(printCSV), keymatch.getQuery(),
        keymatch.getUrl(), keymatch.getKeyMatchType(), keymatch.getTitle(),
        verifyURL ? keymatch.getUrlStatusString() : "Not tested"));
  }

  private static String getFormatString(final boolean printCSV) {
    final String FMT_STR = "%-15s %-25.25s %-15.15s %10.10s %5s";
    final String CSV_FMT_STR = "%s,%s,%s,%s,%s";
    return printCSV ? CSV_FMT_STR : FMT_STR;
  }
  
  private static void printErrorAndExit(Exception e, int exitCode) {
    err.println(e.getLocalizedMessage());
    exit(exitCode);
  }

} // End class KeyMatchReport